#include <cstdlib>
#include <iostream>
#include <string>
#include <fstream>
#include <vector>

#include <fbxsdk.h>

#include "../Common/Common.h"




class MyMemoryAllocator {
public:
	static void* MyMalloc(size_t pSize)
	{
		char *p = (char*) malloc(pSize + 1);
		*p = '#';
		return p + 1;
	}

	static void* MyCalloc(size_t pCount, size_t pSize)
	{
		char *p = (char*) calloc(pCount, pSize + 1);
		*p = '#';
		return p + 1;
	}

	static void* MyRealloc(void* pData, size_t pSize)
	{
		if (pData) {
			//FBX_ASSERT(*((char*) pData - 1) == '#');
			if (*((char*) pData - 1) == '#') {
				char *p = (char*) realloc((char*) pData - 1, pSize + 1);
				*p = '#';
				return p + 1;
			} else {   // Mismatch
				char *p = (char*) realloc((char*) pData, pSize + 1);
				*p = '#';
				return p + 1;
			}
		} else {
			char *p = (char*) realloc(NULL, pSize + 1);
			*p = '#';
			return p + 1;
		}
	}

	static void MyFree(void* pData)
	{
		if (pData == NULL)
			return;
		//FBX_ASSERT(*((char*) pData - 1) == '#');
		if (*((char*) pData - 1) == '#') {
			free((char*) pData - 1);
		} else {   // Mismatch
			free(pData);
		}
	}
};


const int TRIANGLE_VERTEX_COUNT = 3;

// Four floats for every position.
const int VERTEX_STRIDE = 4;
// Three floats for every normal.
const int NORMAL_STRIDE = 3;
// Two floats for every UV.
const int UV_STRIDE = 2;

std::vector<FbxMesh*> meshes;
std::vector<FbxNode*> nodes;

void loadFbxNodeHierarchy(FbxNode* node) {

	const FbxNodeAttribute* lNodeAttribute = node->GetNodeAttribute();

	if (lNodeAttribute) {
		if (lNodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh) {
			FbxMesh* mesh = node->GetMesh();
			meshes.push_back(mesh);
			nodes.push_back(node);
		}
	}


	int count = node->GetChildCount();
	for (int i = 0; i != count; i++) {
		loadFbxNodeHierarchy(node->GetChild(i));
	}
}

void loadFbxMesh(FbxMesh* mesh, const std::string& name, std::ostream& output) {
	bool mHasNormal = mesh->GetElementNormalCount() > 0;
	bool mHasUV = mesh->GetElementUVCount() > 0;
	bool mAllByControlPoint = true;

	const int lPolygonCount = mesh->GetPolygonCount();

	FbxGeometryElement::EMappingMode lNormalMappingMode = FbxGeometryElement::eNone;
	FbxGeometryElement::EMappingMode lUVMappingMode = FbxGeometryElement::eNone;

	if (mHasNormal) {
		lNormalMappingMode = mesh->GetElementNormal(0)->GetMappingMode();
		if (lNormalMappingMode == FbxGeometryElement::eNone) {
			mHasNormal = false;
		}
		if (mHasNormal && lNormalMappingMode != FbxGeometryElement::eByControlPoint) {
			mAllByControlPoint = false;
		}
	}
	if (mHasUV) {
		lUVMappingMode = mesh->GetElementUV(0)->GetMappingMode();
		if (lUVMappingMode == FbxGeometryElement::eNone) {
			mHasUV = false;
		}
		if (mHasUV && lUVMappingMode != FbxGeometryElement::eByControlPoint) {
			mAllByControlPoint = false;
		}
	}


	// Allocate the array memory, by control point or by polygon vertex.
	int lPolygonVertexCount = mesh->GetControlPointsCount();
	if (!mAllByControlPoint) {
		lPolygonVertexCount = lPolygonCount * TRIANGLE_VERTEX_COUNT;
	}
	float * lVertices = new float[lPolygonVertexCount * VERTEX_STRIDE];
	unsigned int * lIndices = new unsigned int[lPolygonCount * TRIANGLE_VERTEX_COUNT];
	float * lNormals = NULL;
	if (mHasNormal) {
		lNormals = new float[lPolygonVertexCount * NORMAL_STRIDE];
	}
	float * lUVs = NULL;
	FbxStringList lUVNames;
	mesh->GetUVSetNames(lUVNames);
	const char * lUVName = NULL;
	if (mHasUV && lUVNames.GetCount()) {
		lUVs = new float[lPolygonVertexCount * UV_STRIDE];
		lUVName = lUVNames[0];
	}

	// Populate the array with vertex attribute, if by control point.
	const FbxVector4 * lControlPoints = mesh->GetControlPoints();
	FbxVector4 lCurrentVertex;
	FbxVector4 lCurrentNormal;
	FbxVector2 lCurrentUV;

	int lVertexCount = 0;
	for (int lPolygonIndex = 0; lPolygonIndex < lPolygonCount; ++lPolygonIndex) {
		for (int lVerticeIndex = 0; lVerticeIndex < TRIANGLE_VERTEX_COUNT; ++lVerticeIndex) {
			lIndices[lVertexCount] = static_cast<unsigned int>(lVertexCount);

			const int lControlPointIndex = mesh->GetPolygonVertex(lPolygonIndex, lVerticeIndex);
			lCurrentVertex = lControlPoints[lControlPointIndex];

			lVertices[lVertexCount * VERTEX_STRIDE] = static_cast<float>(lCurrentVertex[0]);
			lVertices[lVertexCount * VERTEX_STRIDE + 1] = static_cast<float>(lCurrentVertex[1]);
			lVertices[lVertexCount * VERTEX_STRIDE + 2] = static_cast<float>(lCurrentVertex[2]);
			lVertices[lVertexCount * VERTEX_STRIDE + 3] = 1;

			if (mHasNormal) {
				mesh->GetPolygonVertexNormal(lPolygonIndex, lVerticeIndex, lCurrentNormal);
				lNormals[lVertexCount * NORMAL_STRIDE] = static_cast<float>(lCurrentNormal[0]);
				lNormals[lVertexCount * NORMAL_STRIDE + 1] = static_cast<float>(lCurrentNormal[1]);
				lNormals[lVertexCount * NORMAL_STRIDE + 2] = static_cast<float>(lCurrentNormal[2]);
			}

			if (mHasUV) {
				bool lUnmappedUV;
				mesh->GetPolygonVertexUV(lPolygonIndex, lVerticeIndex, lUVName, lCurrentUV, lUnmappedUV);
				lUVs[lVertexCount * UV_STRIDE] = static_cast<float>(lCurrentUV[0]);
				lUVs[lVertexCount * UV_STRIDE + 1] = static_cast<float>(lCurrentUV[1]);
			}
			++lVertexCount;
		}
	}

	output << "\n\n\n" << name << "\n";

	output << "logo.jpg\n";


	//Geometry begin
	output << "false 3 vPosition vNormal vTexcoord\n";
	output << "TRIANGLES " << lVertexCount << "\n";

	output << "\nvPosition 3\n";
	for (int i = 0; i != lVertexCount; i++) {
		if (lVertices[i * 4 + 0] > -1e-5f && lVertices[i * 4 + 0] < 1e-5f) lVertices[i * 4 + 0] = 0;
		if (lVertices[i * 4 + 1] > -1e-5f && lVertices[i * 4 + 1] < 1e-5f) lVertices[i * 4 + 1] = 0;
		if (lVertices[i * 4 + 2] > -1e-5f && lVertices[i * 4 + 2] < 1e-5f) lVertices[i * 4 + 2] = 0;
		output << lVertices[i * 4 + 0] << " " << lVertices[i * 4 + 1] << " " << lVertices[i * 4 + 2] << "\n";
	}

	if (mHasNormal) {
		output << "\nvNormal 3\n";
		for (int i = 0; i != lVertexCount; i++) {
			if (lNormals[i * 3 + 0] > -1e-5f && lNormals[i * 3 + 0] < 1e-5f) lNormals[i * 3 + 0] = 0;
			if (lNormals[i * 3 + 1] > -1e-5f && lNormals[i * 3 + 1] < 1e-5f) lNormals[i * 3 + 1] = 0;
			if (lNormals[i * 3 + 2] > -1e-5f && lNormals[i * 3 + 2] < 1e-5f) lNormals[i * 3 + 2] = 0;
			output << lNormals[i * 3 + 0] << " " << lNormals[i * 3 + 1] << " " << lNormals[i * 3 + 2] << "\n";
		}
	}

	if (mHasUV) {
		output << "\nvTexcoord 2\n";
		for (int i = 0; i != lVertexCount; i++) {
			if (lUVs[i * 2 + 0] > -1e-5f && lUVs[i * 2 + 0] < 1e-5f) lUVs[i * 2 + 0] = 0;
			if (lUVs[i * 2 + 1] > -1e-5f && lUVs[i * 2 + 1] < 1e-5f) lUVs[i * 2 + 1] = 0;
			output << lUVs[i * 2 + 0] << " " << lUVs[i * 2 + 1] << "\n";
		}
	}
	//Geometry end

	std::cout << mesh->GetControlPointsCount() << " " << name << std::endl;
}

int main(int argc, char** argv) {

	// Use a custom memory allocator
	FbxSetMallocHandler(MyMemoryAllocator::MyMalloc);
	FbxSetReallocHandler(MyMemoryAllocator::MyRealloc);
	FbxSetFreeHandler(MyMemoryAllocator::MyFree);
	FbxSetCallocHandler(MyMemoryAllocator::MyCalloc);

	enum Status {
		UNLOADED,               // Unload file or load failure;
		MUST_BE_LOADED,         // Ready for loading file;
		MUST_BE_REFRESHED,      // Something changed and redraw needed;
		REFRESHED               // No redraw needed.
	} mStatus = UNLOADED;

	FbxString mFileName("./Resource/logo.fbx");
	FbxString mWindowMessage;


	FbxManager * mSdkManager;
	FbxScene * mScene;
	FbxImporter * mImporter = nullptr;

	//int mPoseIndex;
	FbxArray<FbxString*> mAnimStackNameArray;
	FbxArray<FbxNode*> mCameraArray;
	FbxArray<FbxPose*> mPoseArray;

	FbxTime mFrameTime, mStart, mStop, mCurrentTime;
	FbxTime mCache_Start, mCache_Stop;

	InitializeSdkObjects(mSdkManager, mScene);


	//Initialize SDK.
	if (mSdkManager) {
		// Create the importer.
		int lFileFormat = -1;
		mImporter = FbxImporter::Create(mSdkManager, "");
		if (!mSdkManager->GetIOPluginRegistry()->DetectReaderFileFormat(mFileName, lFileFormat)) {
			// Unrecognizable file format. Try to fall back to FbxImporter::eFBX_BINARY
			lFileFormat = mSdkManager->GetIOPluginRegistry()->FindReaderIDByDescription("FBX binary (*.fbx)");;
		}

		// Initialize the importer by providing a filename.
		if (mImporter->Initialize(mFileName, lFileFormat) == true) {
			// The file is going to be imported at 
			// the end of the first display callback.
			mWindowMessage = "Importing file ";
			mWindowMessage += mFileName;
			mWindowMessage += "\nPlease wait!";

			// Set scene status flag to ready to load.
			mStatus = MUST_BE_LOADED;
		} else {
			mWindowMessage = "Unable to open file ";
			mWindowMessage += mFileName;
			mWindowMessage += "\nError reported: ";
			mWindowMessage += mImporter->GetStatus().GetErrorString();
			mWindowMessage += "\nEsc to exit";
		}
	} else {
		mWindowMessage = "Unable to create the FBX SDK manager";
		mWindowMessage += "\nEsc to exit";
	}


	//Import scene.
	// Make sure that the scene is ready to load.
	if (mStatus == MUST_BE_LOADED) {
		if (mImporter && mImporter->Import(mScene) == true) {
			// Set the scene status flag to refresh 
			// the scene in the first timer callback.
			mStatus = MUST_BE_REFRESHED;

			// Convert Axis System to what is used in this example, if needed
			FbxAxisSystem SceneAxisSystem = mScene->GetGlobalSettings().GetAxisSystem();
			FbxAxisSystem OurAxisSystem(FbxAxisSystem::eYAxis, FbxAxisSystem::eParityOdd, FbxAxisSystem::eRightHanded);
			if (SceneAxisSystem != OurAxisSystem) {
				OurAxisSystem.ConvertScene(mScene);
			}

			// Convert Unit System to what is used in this example, if needed
			FbxSystemUnit SceneSystemUnit = mScene->GetGlobalSettings().GetSystemUnit();
			if (SceneSystemUnit.GetScaleFactor() != 1.0) {
				//The unit in this example is centimeter.
				FbxSystemUnit::cm.ConvertScene(mScene);
			}

			// Get the list of all the animation stack.
			mScene->FillAnimStackNameArray(mAnimStackNameArray);

			// Get the list of all the cameras in the scene.
			//FillCameraArray(mScene, mCameraArray);

			// Convert mesh, NURBS and patch into triangle mesh
			FbxGeometryConverter lGeomConverter(mSdkManager);
			lGeomConverter.Triangulate(mScene, /*replace*/true);

			// Split meshes per material, so that we only have one material per mesh (for VBO support)
			lGeomConverter.SplitMeshesPerMaterial(mScene, /*replace*/true);

			// Bake the scene for one frame
			//LoadCacheRecursive(mScene, mCurrentAnimLayer, mFileName, mSupportVBO);

			// Convert any .PC2 point cache data into the .MC format for 
			// vertex cache deformer playback.
			//PreparePointCacheData(mScene, mCache_Start, mCache_Stop);

			// Get the list of pose in the scene
			//FillPoseArray(mScene, mPoseArray);

			// Initialize the frame period.
			mFrameTime.SetTime(0, 0, 0, 1, 0, mScene->GetGlobalSettings().GetTimeMode());
		} else {
			// Import failed, set the scene status flag accordingly.
			mStatus = UNLOADED;

			mWindowMessage = "Unable to import file ";
			mWindowMessage += mFileName;
			mWindowMessage += "\nError reported: ";
			mWindowMessage += mImporter->GetStatus().GetErrorString();
		}

		// Destroy the importer to release the file.
		mImporter->Destroy();
		mImporter = NULL;
	}


	//Load scene
	if (mStatus == MUST_BE_REFRESHED) {

		FbxTakeInfo* lCurrentTakeInfo = mScene->GetTakeInfo(*(mAnimStackNameArray[0]));
		if (lCurrentTakeInfo) {
			mStart = lCurrentTakeInfo->mLocalTimeSpan.GetStart();
			mStop = lCurrentTakeInfo->mLocalTimeSpan.GetStop();
		} else {
			// Take the time line value
			FbxTimeSpan lTimeLineTimeSpan;
			mScene->GetGlobalSettings().GetTimelineDefaultTimeSpan(lTimeLineTimeSpan);

			mStart = lTimeLineTimeSpan.GetStart();
			mStop = lTimeLineTimeSpan.GetStop();
		}

		int frameCount = (mStop - mStart).Get() / mFrameTime.Get() + 1;

		std::cout << "frameCount = " << frameCount << ", mStart = " << mStart.GetMilliSeconds() << ", mStop = " << mStop.GetMilliSeconds() << std::endl;

		loadFbxNodeHierarchy(mScene->GetRootNode());

		int meshCount = meshes.size();

		std::ofstream output;
		output.open("./Output/output.mesh");

		output << meshCount << "\n";

		for (int i = 0; i != meshCount; i++) {
			loadFbxMesh(meshes[i], std::string(nodes[i]->GetName()), output);
		}

		output << "\n\n1\n";
		output << "idle\n";

		output << frameCount << "\n\n";

		for (int i = 0; i != frameCount; i++) {
			FbxTime currentTime = mFrameTime * i;
			output << currentTime.GetMilliSeconds() - (mFrameTime * (i-1)).GetMilliSeconds() << "\n";
			for (int j = 0; j != meshCount; j++) {
				FbxNode* node = nodes[j];

				FbxAMatrix matrix = node->EvaluateGlobalTransform(currentTime);
				for (int x = 0; x != 4; x++) {
					for (int y = 0; y != 4; y++) {
						float f = matrix.Get(x, y);
						if (-1e-5f < f && f < 1e-5f) f = 0.f;
						output << f << " ";
					}
					output << "\n";
				}
				output << "\n";
			}
		}

		output.close();

	}

	system("pause");

	return 0;
}
